package xj.graph2d;

import static xj.graph2d.DrawShapeConstants.PointGraphEdgeDestination;
import static xj.graph2d.DrawShapeConstants.PointGraphEdgeSource;
import static xj.graph2d.DrawShapeConstants.PointInBounds;
import static xj.graph2d.DrawShapeConstants.PointInside;
import static xj.graph2d.DrawShapeConstants.PointNECorner;
import static xj.graph2d.DrawShapeConstants.PointNWCorner;
import static xj.graph2d.DrawShapeConstants.PointOnEnd;
import static xj.graph2d.DrawShapeConstants.PointOutside;
import static xj.graph2d.DrawShapeConstants.PointSECorner;
import static xj.graph2d.DrawShapeConstants.PointSWCorner;
import static xj.graph2d.DrawShapeConstants.d;
import static xj.graph2d.DrawShapeConstants.markStroke;

import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.util.HashMap;
import java.util.Map;

import xj.graph2d.graph.GraphNode;
import xj.graph2d.util.DrawingUtilities;

public class LineShape 
  extends TwoEndsEdgeShape {

  public LineShape() {
    super(false); // no graph componnet
    allowRotation = false;
  }

  public LineShape(float x1, float y1, float x2, float y2) {
    super(false); // no graph componnet
    setEnds(x1, y1, x2, y2);
    allowRotation = false;
  }

  public LineShape(boolean createComp) {
    super(createComp);
    allowRotation = false;
  }

  public LineShape(float x1, float y1, float x2, float y2, boolean createComp) {
    super(createComp);
    setEnds(x1, y1, x2, y2);
    allowRotation = false;
  }

  @Override
  public String getName() {
    return "Line";
  }

  public DrawShape makeInstance() {
    return new LineShape(fullCloneMode && comp != null);
  }

  @Override
  public void copyTo(DrawShapeBase s) 
    throws CloneNotSupportedException {
    if (s != null) {
      super.copyTo(s);
      if (s instanceof LineShape) {
	LineShape l = (LineShape) s;
	l.vertical = vertical;
	l.horizontal = horizontal;
      }
    }
  }

  public boolean isVertical() {
    return vertical;
  }

  public void setVertical(boolean b) {
    vertical = b;
  }

  public boolean isHorizontal() {
    return horizontal;
  }

  public void setHorizontal(boolean b) {
    horizontal = b;
  }

  @Override
  public boolean isEdgeSensitive() {
    return false;
  }

  @Override
  public boolean isCornorSensitive() {
    return true;
  }

  @Override
  public void normalize() {
  }

  @Override
  public void setEnds(float x1, float y1, float x2, float y2) {
    GraphNode src = getSource();
    GraphNode des = getDestination();
    if (vertical) {
      if (des == null) {
	x2 = x1;
      } else if (src == null) {
	x1 = x2;
      } else {
	x2 = x1;
	// float x0 = (x1 + x2) / 2;
	// x1 = x2 = x0;
      }
    }
    if (horizontal) {
      if (des == null) {
	y2 = y1;
      } else if (src == null) {
	y1 = y2;
      } else {
	y2 = y1;
	// float y0 = (y1 + y2) / 2;
	// y1 = y2 = y0;
      }
    }
    super.setEnds(x1, y1, x2, y2);
  }

  @Override
  public void moveCorner(int pos, float dx, float dy, boolean scaleSubshapes) {
    if (true) {
      System.out.println("LineShape.moveCorner() pos=" + pos + " dx=" + dx + " dy=" + dy + 
			 " vertical=" + vertical + " horizontal=" + horizontal);
    }

    switch (pos) {
    case PointNWCorner:
    case PointSWCorner:
      p1.setLocation((float) p1.getX() + dx, (float) p1.getY() + dy);
      if (vertical) {
	p2.setLocation((float) p2.getX() + dx, (float) p2.getY());
      } else if (horizontal) {
	p2.setLocation((float) p2.getX(), (float) p2.getY() + dy);
      }
      if (vertical || horizontal) {
	adjustHeadOffset();
      }
      break;
    case PointNECorner:
    case PointSECorner:
      p2.setLocation((float) p2.getX() + dx, (float) p2.getY() + dy);
      if (vertical) {
	p1.setLocation((float) p1.getX() + dx, (float) p1.getY());
      } else if (horizontal) {
	p1.setLocation((float) p1.getX(), (float) p1.getY() + dy);
      }
      if (vertical || horizontal) {
	adjustTailOffset();
      }
      break;
    }

    calculateBounds();
  }

  @Override
  public void scale(float sx, float sy) {
    float x0 = (float) Math.min(p1.getX(), p2.getX());
    float y0 = (float) Math.min(p1.getY(), p2.getY());
    float x1 = (float) p1.getX();
    float y1 = (float) p1.getY();
    float x2 = (float) p2.getX();
    float y2 = (float) p2.getY();
    x1 = x0 + (x1 - x0) * sx;
    y1 = y0 + (y1 - y0) * sy;
    x2 = x0 + (x2 - x0) * sx;
    y2 = y0 + (y2 - y0) * sy;
    p1.setLocation(x1, y1);
    p2.setLocation(x2, y2);
  }

  @Override
  public void draw(Graphics2D g2) {
    if (head != null || tail != null) {
      setArrowPosition();
      float x1 = (float) p1.getX();
      float y1 = (float) p1.getY();
      float x2 = (float) p2.getX();
      float y2 = (float) p2.getY();
      double d = Point2D.distance(x1, y1, x2, y2);
      double headWidth = 0;
      double tailWidth = 0;
      if (head != null) {
	headWidth = head.getWidth();
	if (headWidth < d) {
	  head.draw(g2);
	  Point2D p = head.getTailPosition();
	  x2 = (float) p.getX();
	  y2 = (float) p.getY();
	} else {
	  headWidth = 0;
	}
      }
      if (tail != null) {
	tailWidth = tail.getWidth();
	if (tailWidth < (d - headWidth)) {
	  tail.draw(g2);
	  Point2D p = tail.getTailPosition();
	  x1 = (float) p.getX();
	  y1 = (float) p.getY();
	}
      }
      g2.draw(new Line2D.Float(x1, y1, x2, y2));
    } else {
      g2.draw(new Line2D.Float(p1, p2));
    }
  }

  @Override
  public void drawOutline(Graphics2D g2, float x1, float y1, float x2, float y2) {
    DrawAttr attr = getDrawAttr();
    if (attr != null) {
      g2.setColor(attr.getPenColor());
    } else {
      g2.setColor(DrawAttr.getDefaultPenColor());
    }
    if (vertical) {
      x2 = x1;
    }
    if (horizontal) {
      y2 = y1;
    }
    g2.draw(new Line2D.Float(x1, y1, x2, y2));
    // drawAdornments(g2);
  }

  @Override
  public void drawFrame(Graphics2D g2) {
    int penSize = 1;
    DrawAttr attr = getDrawAttr();
    if (attr != null) {
      penSize = attr.penSize;
    }

    int marksize = DrawingUtilities.getMarkSize(penSize);
    g2.setStroke(markStroke);
    DrawingUtilities.drawLineFrame(g2, p1, p2, marksize,
				   true, true, 
				   getSource() != null, 
				   getDestination() != null);
  }

  @Override
  public Shape getOutlineShape() {
    return new Line2D.Float(p1, p2);
  }

  @Override
  public void setArrowPosition() {
    float x1 = (float) p1.getX();
    float y1 = (float) p1.getY();
    float x2 = (float) p2.getX();
    float y2 = (float) p2.getY();
    setArrowPosition(x1, y1, x2, y2);
  }

  public void setArrowPosition(float x1, float y1, float x2, float y2) {
    if (head != null) {
      head.setPosition(x2, y2);
      head.setAngle((float) DrawingUtilities.calculateRotationAngle(x2, y2, x1, y1));
    }
    if (tail != null) {
      tail.setPosition(x1, y1);
      tail.setAngle((float) DrawingUtilities.calculateRotationAngle(x1, y1, x2, y2));
    }
  }

  @Override
  public int isInside(Point2D p) {
    if (p != null) {
      float px = (float) p.getX();
      float py = (float) p.getY();
      return isInside(px, py);
    } else {
      return PointOutside;
    }
  }

  public float getWidthSensitivity() {
    return d;
  }

  @Override
  public int isInside(float px, float py) {
    float d1 = getWidthSensitivity();
    if (attr != null && attr.penSize > 1) {
      d1 += (attr.penSize / 2);
    }
    int result = PointOutside;
    int edgeResult = 0;
    double x1 = bounds.getX();
    double y1 = bounds.getY();
    double x2 = (bounds.getX() + bounds.getWidth());
    double y2 = (bounds.getY() + bounds.getHeight());
    if ((px >= x1 - d1) && 
	(px <= x2 + d1) && 
	(py >= y1 - d1)	&& 
	(py <= y2 + d1)) {
      result = PointInBounds;
      Line2D line = new Line2D.Float(p1, p2);
      double dist = line.ptLineDist(px, py);
      if (dist <= d1) {
	// close enough to the line to be considered on the line
	result = PointInside;
	double dx1 = Math.abs(px - p1.getX());
	double dy1 = Math.abs(py - p1.getY());
	double dx2 = Math.abs(px - p2.getX());
	double dy2 = Math.abs(py - p2.getY());
	if (dx1 <= d1 && 
	    dy1 <= d1) {
	  result = PointOnEnd;
	  edgeResult = PointGraphEdgeSource;
	} else if (dx2 <= d1 && 
		   dy2 <= d1) {
	  result = PointOnEnd;
	  edgeResult = PointGraphEdgeDestination;
	}
      }
    }

    result += edgeResult;

    if (debug) { 
      System.out.println("LineShape.isInside()=" + Integer.toHexString(result));
    }
    return result;
  }

  @Override
  protected Map<String, Object> getXMLAttributes() {
    Map<String, Object> attrs = super.getXMLAttributes();
    if (attrs == null) {
      attrs = new HashMap<String, Object>();
    }
    attrs.put("vertical", vertical);
    attrs.put("horizontal", horizontal);
    return attrs;
  }

  @Override
  protected void processXMLAttributes(org.w3c.dom.Element node) {
    if (node != null) {
      super.processXMLAttributes(node);

      String bs = node.getAttribute("vertical");
      vertical = Boolean.valueOf(bs);
      bs = node.getAttribute("horizontal");
      horizontal = Boolean.valueOf(bs);
    }
  }

  protected boolean vertical = false, horizontal = false;

}
